/**
 * Copyright (c) 2001, Sergey A. Samokhodkin
 * All rights reserved.
 * <br>
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * <br>
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form
 * must reproduce the above copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the distribution.
 * - Neither the name of jregex nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior
 * written permission.
 * <br>
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @version 1.2_01
 */

package regexodus;

import com.jtransc.annotation.JTranscInvisible;

@JTranscInvisible
class Optimizer {
    private static final int THRESHOLD = 20;

    static Optimizer find(Term entry) {
        return find(entry, 0);
    }

    private static Optimizer find(Term term, int dist) {
//System.out.println("term="+term+", dist="+dist);
        if (term == null) return null;
        Term next = term.next;
        int type = term.type;
        switch (type) {
            case Term.CHAR:
            case Term.REG:
            case Term.REG_I:
            case Term.GROUP_IN:
                return new Optimizer(term, dist);
            case Term.BITSET:
            case Term.BITSET2:
                if (term.weight <= THRESHOLD) return new Optimizer(term, dist);
                else return find(term.next, dist + 1);
            case Term.ANY_CHAR:
            case Term.ANY_CHAR_NE:
                return find(next, dist + 1);
            case Term.REPEAT_MIN_INF:
            case Term.REPEAT_MIN_MAX:
                if (term.minCount > 0) {
                    return find(term.target, dist);
                } else return null;
        }
        if (type >= Term.FIRST_TRANSPARENT && type <= Term.LAST_TRANSPARENT) {
            return find(next, dist);
        }
        return null;
    }

    private Term atom;
    private int distance;

    private Optimizer(Term atom, int distance) {
        this.atom = atom;
        this.distance = distance;
    }

    Term makeFirst(Term theFirst) {
        return new Find(atom, distance, theFirst);
    }

    Term makeBacktrack(Term back) {
        int min = back.minCount;
        switch (back.type) {
            case Term.BACKTRACK_0:
                min = 0;
            case Term.BACKTRACK_MIN:
                return new FindBack(atom, distance, min, back);

            case Term.BACKTRACK_REG_MIN:
                return back;

            default:
                throw new Error("unexpected iterator's backtracker:" + back);
                //return back;
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Optimizer optimizer = (Optimizer) o;

        return distance == optimizer.distance && (atom != null ? atom.equals(optimizer.atom) : optimizer.atom == null);

    }

    @Override
    public int hashCode() {
        int result = atom != null ? atom.hashCode() : 0;
        result = 31 * result + distance;
        return result;
    }
}

@JTranscInvisible
class Find extends Term {
    Find(Term target, int distance, Term theFirst) {
        switch (target.type) {
            case Term.CHAR:
            case Term.BITSET:
            case Term.BITSET2:
                type = Term.FIND;
                break;
            case Term.REG:
            case Term.REG_I:
            case Term.GROUP_IN:
                type = Term.FINDREG;
                break;
            default:
                throw new IllegalArgumentException("wrong target type: " + Term.termLookup(target.type));
        }
        this.target = target;
        this.distance = distance;
        if (target == theFirst) {
            next = target.next;
            eat = true; //eat the next
        } else {
            next = theFirst;
            eat = false;
        }
    }
}

@JTranscInvisible
class FindBack extends Term {
    FindBack(Term target, int distance, int minCount, Term backtrack) {
        this.minCount = minCount;
        switch (target.type) {
            case Term.CHAR:
            case Term.BITSET:
            case Term.BITSET2:
                type = Term.BACKTRACK_FIND_MIN;
                break;
            case Term.REG:
            case Term.REG_I:
            case Term.GROUP_IN:
                type = Term.BACKTRACK_FINDREG_MIN;
                break;
            default:
                throw new IllegalArgumentException("wrong target type: " + Term.termLookup(target.type));
        }

        this.target = target;
        this.distance = distance;
        Term next = backtrack.next;
        if (target == next) {
            this.next = next.next;
            this.eat = true;
        } else {
            this.next = next;
            this.eat = false;
        }
    }
}